Skip to content

Fix iOS composer compile break (nonisolated image helpers)#6198

Closed
lawrencecchen wants to merge 2 commits into
mainfrom
fix-ios-composer-actor
Closed

Fix iOS composer compile break (nonisolated image helpers)#6198
lawrencecchen wants to merge 2 commits into
mainfrom
fix-ios-composer-actor

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

main's iOS build is currently broken. TerminalComposerView: View is implicitly @MainActor, so its static boundedSendPayload / downsampledImageData are main-actor-isolated, but the nonisolated prepare(url:) calls them synchronously off-actor inside a background TaskGroup. ios-simulator fails to compile:

TerminalComposerView.swift:516: error: main actor-isolated static method 'boundedSendPayload(from:)' cannot be called from outside of the actor
TerminalComposerView.swift:520: error: expression is 'async' but is not marked with 'await'

These are pure ImageIO functions with no main-actor state (the doc comment already says the decode must not run on the main actor), so both are marked nonisolated. The size constants they read are static let Sendable Ints, already nonisolated-accessible; PreparedAttachment is a Sendable nested struct (no inherited isolation).

Landed on main via #6102 because ios-simulator is not a required check (iOS CI false-green). Unblocks the in-flight iOS PRs (#6038, #6044, #6096, #6119) which all merged main and inherited the break.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Isolation annotations only; image encode logic and store caps are unchanged.

Overview
Fixes the iOS simulator compile failure where background image staging called main-actor-isolated static helpers on TerminalComposerView.

prepare(url:) already runs ImageIO in a background TaskGroup; boundedSendPayload and downsampledImageData are now nonisolated so they can be invoked from that path without crossing the main actor. Related size constants (maxImageBytes, sendMaxPixelSize, thumbnailMaxPixelSize) and CMUXMobileShellStore.maxPendingAttachmentImageBytes are also marked nonisolated so the off-main encode path can read the same caps. No change to attachment limits or encoding behavior.

Reviewed by Cursor Bugbot for commit ffadba7. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fix iOS build by marking TerminalComposerView image helpers and size-cap constants as nonisolated so background calls from prepare(url:) compile under Swift 6. Keeps ImageIO work off the main thread and removes the main-actor isolation error in the iOS simulator.

  • Bug Fixes
    • Marked boundedSendPayload and downsampledImageData in TerminalComposerView as nonisolated.
    • Marked maxImageBytes, thumbnailMaxPixelSize, sendMaxPixelSize, and the store’s maxPendingAttachmentImageBytes as nonisolated for off-actor reads.
    • No behavioral changes; isolation-only update.

Written for commit ffadba7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Chores
    • Updated terminal composer image-encoding helpers to use nonisolated static declarations, improving correctness/performance in off-main encoding paths.
    • Marked the exported pending-attachment image size limit as nonisolated for safe reads from the composer image-encoding workflow.
    • No changes to image size limits, encoding results, or other user-facing behavior.

TerminalComposerView is a View (implicitly @mainactor), so its static
boundedSendPayload / downsampledImageData were main-actor-isolated, but
prepare(url:) is nonisolated and calls them synchronously off-actor inside
its background TaskGroup. That fails to compile for ios-simulator
("main actor-isolated static method ... cannot be called from outside of
the actor" + "expression is 'async' but is not marked with await").

These are pure ImageIO functions with no main-actor state (the doc comment
already states the decode must not run on the main actor), so mark both
nonisolated. Referenced size constants are static let Sendable Ints, already
nonisolated-accessible. Landed on main via #6102 because ios-simulator is
not a required check (iOS CI false-green).
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 16, 2026 12:59am
cmux-staging Building Building Preview, Comment Jun 16, 2026 12:59am

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Image-encoding helpers and size constants across two modules are marked nonisolated static to enable safe concurrent access from off-main composer paths. A shared public byte-capacity constant in MobileShellComposite is exported as nonisolated static so that TerminalComposerView can read it safely from nonisolated contexts. Private encoding constants and methods in TerminalComposerView are similarly annotated. No implementation or call-site logic changes.

Image-encoding concurrency isolation

Layer / File(s) Summary
Authorize external nonisolated access to image cap
Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
maxPendingAttachmentImageBytes is marked public nonisolated static with a comment noting its use in composer off-main image-encoding paths.
Mark private encoding constants and methods nonisolated
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift
Encoding size constants maxImageBytes and sendMaxPixelSize, and helper methods boundedSendPayload(from:) and downsampledImageData(...), are marked private nonisolated static.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

  • manaflow-ai/cmux#6102: Concurrency annotations on image-encoding helpers directly support the image-attachment composer send path added in that PR.

Poem

🐇 Hop, hop — constants set free,
No actor to bind them, just pure concurrency.
nonisolated stamped from core to the view,
The image encoders have work they can do.
Safe off the main thread—the bunny's delight! 🌿


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Cmux Swift File And Package Boundaries ❌ Error PR adds new MobileShellComposite.swift (5553 lines), far exceeding 800-line threshold, mixing connection state, terminal management, workspace actions, event streaming, and attachment logic—violati... Extract domain logic from MobileShellComposite into separate focused packages (e.g., MobileConnection, MobileTerminalSession, MobileWorkspaceActions) to keep individual files under 800 lines with single clear responsibility.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing an iOS compiler issue by marking image helpers as nonisolated.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR marks pure ImageIO methods and immutable Sendable constants as nonisolated—correctly restores off-MainActor calls in background TaskGroup, fixing a Swift 6 compilation error without introducing...
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking/timing-based synchronization: only adds 'nonisolated' annotations to pure image-processing functions and immutable constants for Swift 6 actor-isolation compliance.
Cmux Expensive Synchronous Load ✅ Passed PR marks expensive ImageIO image-encoding functions as nonisolated to move them OFF the main actor, enabling background task execution; does not add expensive sync loads to main actor or interactiv...
Cmux Cache Substitution Correctness ✅ Passed PR contains only Swift concurrency isolation annotations (nonisolated) with no logic changes. No cache substitution or persistence paths involved—check not applicable.
Cmux No Hacky Sleeps ✅ Passed Custom check "cmux no hacky sleeps" explicitly scopes out Swift files (deferred to swift-blocking-runtime.md). This PR modifies only Swift files with concurrency annotations and no timing/delay l...
Cmux Algorithmic Complexity ✅ Passed Changes are purely Swift concurrency isolation annotations (adding nonisolated keyword). No algorithmic changes: function bodies unchanged, no new loops/sorting/filtering, constants values unchan...
Cmux Swift Concurrency ✅ Passed PR only adds nonisolated isolation annotations to existing code; no legacy async patterns (DispatchQueue, Combine, completion handlers, fire-and-forget Tasks) are introduced or expanded.
Cmux Swift @Concurrent ✅ Passed All changes correctly apply nonisolated to synchronous pure helper functions and immutable static constants, with no invalid @concurrent annotations. Changes comply with swift-concurrent-annotati...
Cmux Swift Logging ✅ Passed PR contains no new logging statements or log modifications. Changes are isolation-only (adding nonisolated to static methods/constants), with no violations of swift-logging.md rules.
Cmux User-Facing Error Privacy ✅ Passed Changes are pure Swift concurrency isolation fixes with no user-facing error messages, exposed credentials, vendor/provider names, or sensitive implementation details.
Cmux Full Internationalization ✅ Passed PR contains only Swift concurrency annotations (nonisolated keywords) with no user-facing text, string catalog, or localization changes. Qualifies as allowed developer-only code per i18n rules.
Cmux Swiftui State Layout ✅ Passed This PR contains only actor isolation annotations (adding nonisolated keywords to static functions and constants). There are no new SwiftUI state patterns, GeometryReader uses, lazy list row store...
Cmux Architecture Rethink ✅ Passed Pure concurrency/isolation annotation fix (nonisolated keyword additions) for Swift 6 compilation. No logic changes, no timing constructs, no state additions, no architectural debt introduced—match...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR makes no window-related changes; only adds 'nonisolated' isolation annotations to image encoding helpers and constants. No NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup c...
Cmux Source Artifacts ✅ Passed PR modifies only hand-written Swift source files in standard Packages/*/Sources/ directories with concurrency isolation annotations; no artifacts, generated code, logs, build output, or problematic...
Description check ✅ Passed The pull request description provides a clear summary of what changed (marking methods/constants as nonisolated), why (fixing iOS build compilation errors due to actor isolation), and includes detailed context about the root cause, affected code, and unblocked PRs.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ios-composer-actor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes an iOS simulator compile break introduced by PR #6102, where boundedSendPayload and downsampledImageData inherited implicit @MainActor isolation from TerminalComposerView: View but were called synchronously from the already-nonisolated background prepare(url:) child task.

  • Marks boundedSendPayload, downsampledImageData, and the three static let size constants they read (maxImageBytes, thumbnailMaxPixelSize, sendMaxPixelSize) as nonisolated in TerminalComposerView.
  • Marks MobileShellComposite.maxPendingAttachmentImageBytes (aliased as CMUXMobileShellStore) as nonisolated so the initializer of maxImageBytes can reference it from a nonisolated context — completing the isolation-safe call chain.
  • Runtime behavior is entirely unchanged; only the Swift concurrency isolation annotations are affected.

Confidence Score: 5/5

Safe to merge — purely additive isolation annotations on immutable constants and pure ImageIO functions with no observable behavior change.

All changed symbols are either static let integer literals or pure ImageIO encode/downsample functions. Marking them nonisolated cannot introduce data races because there is no shared mutable state involved. The nonisolated call chain from prepare(url:) through boundedSendPayload and downsampledImageData is now complete and consistent, and the sibling constants that remain main-actor-bound are untouched. No logic, data flow, or error handling was modified.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift Adds nonisolated to five static members (3 let constants, 2 pure ImageIO functions) that are called from the already-nonisolated prepare(url:) background task; no logic changes.
Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Marks maxPendingAttachmentImageBytes as nonisolated static let so the TerminalComposerView.maxImageBytes initializer can safely reference it from a nonisolated context; sibling constants that remain main-actor-bound are unchanged.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant ST as Staging Task (MainActor)
    participant P as prepare(url:) nonisolated static
    participant BG as TaskGroup child (background priority)
    participant BS as boundedSendPayload nonisolated static
    participant DD as downsampledImageData nonisolated static

    ST->>P: await prepare(url:)
    P->>BG: group.addTask(priority: .background)
    BG->>BS: boundedSendPayload(from: source)
    BS->>DD: downsampledImageData(PNG at sendMaxPixelSize)
    DD-->>BS: Data?
    BS->>DD: downsampledImageData(JPEG fallbacks...)
    DD-->>BS: Data?
    BS-->>BG: (data, format)?
    BG->>DD: downsampledImageData(thumbnail at thumbnailMaxPixelSize)
    DD-->>BG: Data?
    BG-->>P: PreparedAttachment?
    P-->>ST: PreparedAttachment?
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant ST as Staging Task (MainActor)
    participant P as prepare(url:) nonisolated static
    participant BG as TaskGroup child (background priority)
    participant BS as boundedSendPayload nonisolated static
    participant DD as downsampledImageData nonisolated static

    ST->>P: await prepare(url:)
    P->>BG: group.addTask(priority: .background)
    BG->>BS: boundedSendPayload(from: source)
    BS->>DD: downsampledImageData(PNG at sendMaxPixelSize)
    DD-->>BS: Data?
    BS->>DD: downsampledImageData(JPEG fallbacks...)
    DD-->>BS: Data?
    BS-->>BG: (data, format)?
    BG->>DD: downsampledImageData(thumbnail at thumbnailMaxPixelSize)
    DD-->>BG: Data?
    BG-->>P: PreparedAttachment?
    P-->>ST: PreparedAttachment?
Loading

Reviews (2): Last reviewed commit: "Composer fix: also mark the size-cap con..." | Re-trigger Greptile

The iOS build runs in Swift 6 mode (-swift-version 6), where static let on a
@mainactor type is also main-actor-isolated. Marking the decode functions
nonisolated surfaced the next error: prepare()/boundedSendPayload() read the
size-cap constants (thumbnailMaxPixelSize, sendMaxPixelSize, maxImageBytes)
from the nonisolated path. Mark those three nonisolated; maxImageBytes derives
from CMUXMobileShellStore.maxPendingAttachmentImageBytes, so mark that store
constant nonisolated too. All are immutable Sendable Int literals with no
main-actor state, and nonisolated static lets stay readable from @mainactor
callers, so no caller breaks.
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Superseded by #6199 (723329f), which landed the identical fix (mark the composer image-encode helpers + size-cap constants + the store's maxPendingAttachmentImageBytes nonisolated) and is already on main. Closing as redundant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant